using System;
using System.Collections.Generic;
using System.Text;
using LibGreen.Jabber.Connection;
using LibGreen.Jabber.ProtocolElements.IqElements;
using LibGreen.Jabber.ProtocolElements;
using LibGreen.Jabber.Utility;
using LibGreen.Jabber.Delegates;

namespace LibGreen.Jabber.Dispatchers
{
    /// <summary>
    /// Processes and dispatches session establishment stanzas
    /// </summary>
    class SessionDispatcher : IDispatcher
    {
        private readonly XmppConnection parentConnection;
        private bool initialized;

        /// <summary>
        /// Initializes a new <see cref="SessionDispatcher"/>
        /// </summary>
        /// <param name="parentConnection">The <see cref="XmppConnection"/> that sends elements to this dispatcher</param>
        public SessionDispatcher(XmppConnection parentConnection)
        {
            this.parentConnection = parentConnection;
        }

        #region IDispatcher Members
        /// <summary>
        /// Initializes the dispatcher by sending an <see cref="IqSessionElement"/>
        /// </summary>
        public void Initialize()
        {
            if (!initialized)
            {
                if (parentConnection.StreamFeatures.IsSessionRequired)
                {
                    parentConnection.SendXmppElement(IqSessionElement.Create());
                }
                else
                {
                    Logging.LogString("Server does not require sessions");
                    // Well we won't really need this, now will we...TODO: what happens without a session?
                    parentConnection.RemoveDispatcher(this);
                }
                initialized = true;
            }
        }

        /// <summary>
        /// Dispatches a newly received <see cref="Element"/> for processing
        /// </summary>
        /// <param name="element">The newly received <see cref="Element"/> that must be processed</param>
        /// <returns><c>true</c> if the dispatcher processed the element, <c>false</c> otherwise</returns>
        public bool DispatchElement(Element element)
        {
            bool retval = false;
            switch (element.Name)
            {
                case "iq":
                    retval = DispatchIqElement(new IqElement(element));
                    break;
            }
            return retval;
        }

        /// <summary>
        /// Dispatches an <see cref="IqElement"/>
        /// </summary>
        private bool DispatchIqElement(IqElement element)
        {
            Element parent = element.OriginatingElement;
            if (parent != null)
            {
                return ProcessIqResponse(element);
            }
            return false;
        }

        /// <summary>
        /// Processes an <see cref="IqElement"/> that was received in response to an earlier command
        /// </summary>
        /// <param name="element">The <see cref="IqElement"/> containing response data</param>
        private bool ProcessIqResponse(IqElement element)
        {
            bool retval = false;
            string originatingCommand = element.OriginatingElement.Children[0].Name;
            switch (originatingCommand)
            {
                case "session":
                    if (element.Type == IqType.Result)
                    {
                        parentConnection.RemoveDispatcher(this); // No need for further session establishment
                        /* Minor bit of efficiency - message elements are more common than roster elements */
                        parentConnection.AddDispatcher(new MessageDispatcher(parentConnection.Parent));
                        parentConnection.AddDispatcher(new RosterDispatcher(parentConnection.Parent));
                        retval = true;
                    }
                    else
                    {
                        // As far as I can tell, this generally happens because of permissions errors
                        Element error = GetError(element);
                        string type = error.Attributes["type"];
                        string details = error.Children[0].Name;
                        Logging.LogString("Session error: {0} ({1})", type, details);

                        parentConnection.OnConnectionFailed(ConnectionFailureReason.SessionEstablishmentFailure);
                        parentConnection.Disconnect();
                        retval = false;
                    }
                    break;
            }

            return retval;
        }

        /// <summary>
        /// Returns the &lt;error&gt; child of an <see cref="IqElement"/>
        /// </summary>
        /// <param name="element">The <see cref="IqElement"/> to scan for errors</param>
        /// <returns>The &lt;error&gt; child element of the IqElement, or <c>null</c> if none is found</returns>
        private static Element GetError(IqElement element)
        {
            foreach (Element child in element.Children)
            {
                if (child.Name == "error")
                {
                    return child;
                }
            }
            return null;
        }
        #endregion
    }
}
